
/*=====================================================================

  File:      Impersonation.cs
  Summary:   SQL Remote Blob Storage sample provider. Support file for 
             a provider that uses NTFS files as the blob store.
             Class allowing remote file share access via impersonation.
  Date:	     June 24th, 2008

---------------------------------------------------------------------

  This file is part of the Microsoft SQL Server Code Samples.
  Copyright (C) Microsoft Corporation.  All rights reserved.

This source code is intended only as a supplement to Microsoft
Development Tools and/or on-line documentation.  See these other
materials for detailed information regarding Microsoft code samples.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.

======================================================= */

using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Security.Principal;
using Microsoft.Win32.SafeHandles;
using System.Diagnostics.CodeAnalysis;

using Microsoft.Data.SqlRemoteBlobs;
using Microsoft.Data.BlobStores;

namespace Microsoft.Data.BlobStores.FileBlobStore
{
    /// <summary>
    /// Functions that provide wrappers for impersonation apis.
    /// </summary>
    internal
    sealed
    class
    Impersonation : IDisposable
    {

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Constructors

        /// <summary>
        /// Creates an impersonation token using the user credentials passed.
        /// Impersonation APIs do NOT validate the credentials when the token is created. 
        /// If file access fails after invoking impersonation, make sure you check the error code of that API.
        /// For example, if a wrong password is passed to this API, file access API will return ERROR_INVALID_PASSWORD.
        /// </summary>
        [EnvironmentPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        public
        Impersonation(BlobStoreCredentials credentials)
        {
            DebugPrint.WriteLine(OperationType.Test, LogLevel.Verbose,
                "Impersonating {0}", credentials.CredentialName);

            // Split out the domain and user
            //
            string domain = null;
            string user = null;
            SplitUserName(credentials.CredentialName, out domain, out user);

            // Create a pinned byte array wrapped in a GCHandle to contain the unprotected data.
            //
            byte[] unprotectedCreds = new byte[credentials.CredentialSecretLength];
            GCHandle fixedCreds = GCHandle.Alloc(unprotectedCreds, GCHandleType.Pinned);
            try
            {
                // Retrieve the unprotected credentials.
                //
                credentials.DecryptCredentials(unprotectedCreds, 0);

                m_ImpersonationToken = new ImpersonationSafeHandle();
                bool result = NativeMethods.LogonUser(
                                                user,
                                                domain,
                                                unprotectedCreds,
                                                NativeMethods.LOGON32_LOGON_INTERACTIVE,
                                                NativeMethods.LOGON32_PROVIDER_DEFAULT,
                                                ref m_ImpersonationToken
                                                );
                int lastError = Marshal.GetLastWin32Error();
                if (result == false)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.OperationFailedAuthoritative,
                        FileBlobStore.Resources.LogOnUserError, domain, user, lastError);
                }
            }
            finally
            {
                Array.Clear(unprotectedCreds, 0, unprotectedCreds.Length);
                fixedCreds.Free();
            }
        }

        /// <summary>
        /// Dispose all unmanaged resources
        /// </summary>
        public
        void
        Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Finalizer.
        /// Should not be overridden, base classes should override the protected Dispose(bool) 
        /// method. The auto-generated subclass finalizer automatically chains to this base 
        /// finalizer, which will in turn call the correct virtual dispose method.
        /// </summary>
        ~Impersonation()
        {
            Dispose(false);
        }

        /// <summary>
        /// Clean up any handles or state.
        /// </summary>
        public
        void
        Dispose(bool disposing)
        {
            if (!m_Disposed)
            {
                if (disposing)
                {
                    try
                    {
                        // End any current impersonation.
                        //
                        DebugPrint.WriteLine(OperationType.Test, LogLevel.Verbose,
                            "Impersonation.Dispose Called.");

                        if (m_ImpersonationContext != null)
                        {
                            m_ImpersonationContext.Undo();
                        }
                    }
                    finally
                    {
                        // Free managed resources.
                        //
                        if (m_ImpersonationToken != null)
                        {
                            m_ImpersonationToken.Dispose();
                        }

                        if (m_ImpersonationContext != null)
                        {
                            m_ImpersonationContext.Dispose();
                        }
                    }
                }

                // Free native resources.
                // This will be run in the finalizer case as well as explicit dispose.
                //
                m_Disposed = true;
            }
        }

        #endregion

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Public Methods

        /// <summary>
        /// Begins impersonation. 
        /// </summary>
        [EnvironmentPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", 
                         MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle",
                         Justification= "Must retrieve IntPtr for use in WindowsIdentity.Impersonate")]
        public
        void
        BeginImpersonation()
        {
            DebugPrint.WriteLine(OperationType.Test, LogLevel.Verbose,
                "Called BeginImpersonation.");

            m_ImpersonationContext = WindowsIdentity.Impersonate(m_ImpersonationToken.DangerousGetHandle()); 
        }

        /// <summary>
        /// Ends impersonation but does not clean up managed objects.
        /// </summary>
        [EnvironmentPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        public
        void
        EndImpersonation()
        {
            if (m_ImpersonationContext != null)
            {
                m_ImpersonationContext.Undo();
            }
        }

        #endregion

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Private Methods

        /// <summary>
        /// Splits the combined domain\username into individual components.
        /// </summary>
        static
        private
        void
        SplitUserName(string userName, out string domain, out string user)
        {
            user = null;
            domain = null;

            int slashIndex = userName.IndexOf('\\');
            if (slashIndex == -1)
            {
                user = userName;
            } else
            {
                user = userName.Remove(0, slashIndex + 1);
                domain = userName.Substring(0, slashIndex);
            }
        }

        #endregion

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Fields

        private
        ImpersonationSafeHandle m_ImpersonationToken;

        private
        WindowsImpersonationContext m_ImpersonationContext;
        
        private 
        bool m_Disposed;

        #endregion



        //--------------------------------------------------------------------+
        //                                                                    |
        #region Interop Declarations


        /// <summary>
        /// Wraps the impersonation handle returned from LogOnUser.
        /// </summary>
        internal
        sealed
        class ImpersonationSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
            internal
            ImpersonationSafeHandle()
                : base(true) { }

            protected
            override
            bool ReleaseHandle()
            {
                return NativeMethods.CloseHandle(this.handle);
            }
        }

        /// <summary>
        /// Wrapper for DllImport calls.
        /// </summary>
        private
        class
        NativeMethods
        {
            private
            NativeMethods()
            { }

            /// <summary>
            /// LogonUser from advapi32.dll.
            /// </summary>
            [DllImport("advapi32.dll", EntryPoint = "LogonUserW", ExactSpelling = true, CharSet = CharSet.Unicode, 
                       SetLastError = true, CallingConvention = CallingConvention.StdCall)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern
            bool LogonUser(
                string Username,
                string Domain,
                byte[] Password,
                int LogonType,
                int LogonProvider,
                ref ImpersonationSafeHandle Token
                );

            /// <summary>
            /// Closes a Win32 file handle.
            /// </summary>
            [DllImport("Kernel32.dll", EntryPoint = "CloseHandle", ExactSpelling = true, 
                       CallingConvention = CallingConvention.StdCall, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern
            bool CloseHandle(
                IntPtr Handle
                );

            internal static
            int LOGON32_LOGON_INTERACTIVE = 2;

            /// <summary> 
            /// Use the standard logon provider for the system. 
            /// Defaults to 0.
            /// </summary>
            internal static 
            int LOGON32_PROVIDER_DEFAULT = 0;
        }

        #endregion
    }
}

